﻿using MediatR.Wrappers;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MediatR
{
    public class MediatorX : IMediatorX
    {
        private readonly IServiceProvider _serviceProvider;
        private readonly IRouteNotificationPublisher _publisher;

        private static readonly ConcurrentDictionary<Type, RouteNotificationHandlerWrapper> _notificationHandlers = new ConcurrentDictionary<Type, RouteNotificationHandlerWrapper>();

        public MediatorX(IServiceProvider serviceProvider)
            : this(serviceProvider, new ForeachAwaitPublisherX()) { }

        public MediatorX(IServiceProvider serviceProvider, IRouteNotificationPublisher publisher) {

            _serviceProvider = serviceProvider;
            _publisher = publisher;
        }

        /// <summary>
        /// 发布者核心
        /// </summary>
        /// <param name="handlerExecutors"></param>
        /// <param name="routeNotification"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        protected virtual Task PublishCore(IEnumerable<RouteNotificationHandlerExecutor> handlerExecutors, IRouteNotification routeNotification, CancellationToken cancellationToken)
            => _publisher.Publish(handlerExecutors, routeNotification, cancellationToken);

        public Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default) where TNotification : IRouteNotification
        {
            if (notification == null)
            {
                throw new ArgumentNullException(nameof(notification));
            }

            return PublishRouteNotification(notification, cancellationToken);
        }
        public Task Publish(object notification, CancellationToken cancellationToken = default) =>
            notification switch
            {
                null => throw new ArgumentNullException(nameof(notification)),
                IRouteNotification instance => PublishRouteNotification(instance, cancellationToken),
                _ => throw new ArgumentException($"{nameof(notification)} does not implement ${nameof(IRouteNotification)}")
            };

        private Task PublishRouteNotification(IRouteNotification routeNotification, CancellationToken cancellationToken = default)
        {
            var handler = _notificationHandlers.GetOrAdd(routeNotification.GetType(), static notificationType =>
            {
                var wrapperType = typeof(RouteNotificationHandlerWrapperImpl<>).MakeGenericType(notificationType);
                var wrapper = Activator.CreateInstance(wrapperType) ?? throw new InvalidOperationException($"Could not create wrapper for type {notificationType}");
                return (RouteNotificationHandlerWrapper)wrapper;
            });

            return handler.Handle(routeNotification, _serviceProvider, PublishCore, cancellationToken);
        }

    }
}
